var BASIC_CODE = {
    "U1": "D",
    "U2": "E",
    "U3": "G",
    "U4": "G",
    "U5": "J",
    "U6": "A",
    "U7": "A",
    "U8": "C",
    "D1": "P",
    "D2": "M",
    "D3": "S",
    "D4": "O",
    "D5": "X",
    "D6": "I",
    "D7": "M",
    "D8": "K",
    "F1": "B",
    "F2": "B",
    "F3": "L",
    "F4": "S",
    "F5": "T",
    "F6": "N",
    "F7": "R",
    "F8": "Q",
    "B1": "N",
    "B2": "J",
    "B3": "Z",
    "B4": "Y",
    "B5": "H",
    "B6": "F",
    "B7": "F",
    "B8": "U",
    "R1": "K",
    "R2": "H",
    "R3": "I",
    "R4": "Z",
    "R5": "Y",
    "R6": "P",
    "R7": "U",
    "R8": "T",
    "L1": "E",
    "L2": "D",
    "L3": "C",
    "L4": "R",
    "L5": "Q",
    "L6": "L",
    "L7": "O",
    "L8": "X"
};

class Piece {
    constructor(color = "#888888", code = "") {
        this.color = color;
        this.code = code;
    }
}

class Cube {
    constructor() {
        this.colorScheme = {
            'U': 'green',
            'D': 'blue',
            'R': 'orange',
            'L': 'red',
            'F': 'white',
            'B': 'yellow'
        };

        this.pieces = {};
        for (const face in this.colorScheme) {
            for (let i = 0; i < 9; i++) {
                let piece = new Piece(this.colorScheme[face]);
                let name = face + i.toString();
                this.pieces[name] = piece;
            }
        }

        this.corners = [
            ['U5', 'R1', 'F3'],
            ['U7', 'F1', 'L3'],
            ['U1', 'L1', 'B7'],
            ['U3', 'B5', 'R3'],
            ['D7', 'B1', 'L7'],
            ['D1', 'L5', 'F7'],
            ['D3', 'F5', 'R7'],
            ['D5', 'R5', 'B3']
        ];

        this.edges = [
            ['U4', 'R2'],
            ['U6', 'F2'],
            ['U8', 'L2'],
            ['U2', 'B6'],
            ['D4', 'R6'],
            ['D6', 'B2'],
            ['D8', 'L6'],
            ['D2', 'F6'],
            ['F8', 'L4'],
            ['F4', 'R8'],
            ['B8', 'L8'],
            ['B4', 'R4']
        ]

    }

    randCorner() {
        // return a random corner
        let index = Math.floor(Math.random() * 8);
        return this.corners[index];
    }

    randEdge() {
        // return a random edge
        let index = Math.floor(Math.random() * 12);
        return this.edges[index];
    }

    #setColor(face, color) {
        //set color of one desinating face
        if (this.colorScheme.includes(face)) {
            for (var i = 0; i < 9; i++) {
                this.pieces[face + i.toString()].color = color;
            }
        } else {
            throw "Invalid face character";
        }
    }

    #transColorScheme(uColor, fColor) {
        // translate color scheme from given u-face and f-face color to a complete color scheme
        for (var i = 0; i < 3; i++) {
            if (this.colorScheme.U == uColor) {
                break;
            } else {
                this.#rotate('x');
            }
            if (this.colorScheme.U == uColor) {
                break;
            } else {
                this.#rotate('z');
            }
        }

        for (var j = 0; j < 3; j++) {
            if (this.colorScheme.F == fColor) {
                break;
            } else {
                this.#rotate('y');
            }
        }
    }

    #rotate(direction) {
        // valid direction - x, xpi, y, ypi, z, zpi, x2, y2, z2
        let cs = this.colorScheme;
        if (direction == 'x') {
            [cs.U, cs.F, cs.D, cs.B] = [cs.F, cs.D, cs.B, cs.U];
        } else if (direction == 'xpi') {
            [cs.U, cs.B, cs.D, cs.F] = [cs.B, cs.D, cs.F, cs.U];
        } else if (direction == 'y') {
            [cs.F, cs.R, cs.B, cs.L] = [cs.R, cs.B, cs.L, cs.F];
        } else if (direction == 'ypi') {
            [cs.F, cs.L, cs.B, cs.R] = [cs.L, cs.B, cs.R, cs.F];
        } else if (direction == 'z') {
            [cs.U, cs.L, cs.D, cs.R] = [cs.L, cs.D, cs.R, cs.U];
        } else if (direction == 'zpi') {
            [cs.U, cs.R, cs.D, cs.L] = [cs.R, cs.D, cs.L, cs.U];
        } else if (direction == 'x2') {
            [cs.U, cs.D, cs.F, cs.B] = [cs.D, cs.U, cs.B, cs.F];
        } else if (direction == 'y2') {
            [cs.F, cs.B, cs.L, cs.R] = [cs.B, cs.F, cs.R, cs.L];
        } else if (direction == 'z2') {
            [cs.U, cs.D, cs.L, cs.R] = [cs.D, cs.U, cs.R, cs.L];
        } else {
            throw "Invalid direction";
        }
    }

    setColorScheme(uColor, fColor) {
        /**
         * apply color scheme that user designated
         * @param {String} uColor - the color string of u-face
         * @param {String} fColor - the color string of f-face
         */
        this.#transColorScheme(uColor, fColor);
        for (const face in this.colorScheme) {
            for (var i = 0; i < 9; i++) {
                this.pieces[face + i.toString()].color = this.colorScheme[face];
            }
        }
    }

    setCodeScheme(codeScheme) {
        /**
         * save the code scheme which user designated. 
         * @param {Object} codeScheme - an object of a complete code scheme, using key same as such in pieces and customizing code as value.
         */
        for (const piece in codeScheme) {
            this.pieces[piece].code = codeScheme[piece];
        }
    }

    getCodeScheme() {
        let codeScheme = {};
        let piece;
        for (const face of ['U', 'D', 'L', 'R', 'F', 'B']) {
            for (let i = 1; i < 9; i++) {
                piece = face + i.toString();
                codeScheme[piece] = this.pieces[piece].code;
            }
        }
        return codeScheme;
    }
}